# client driver

import sys
import time
import traceback
import threading

import textwrap
import curses
import curses.textpad

from lib import net, event, log, keyboard

class TextpadEvent(event.Event):
    def __init__(self, command):
        super(TextpadEvent, self).__init__('textpad', command)

class TextpadThread(threading.Thread):
    def __init__(self, textpad, eventQueue):
        super(TextpadThread, self).__init__()
        self._eventQueue = eventQueue
        self._textpad = textpad
        self._done = threading.Event()
        
    @property
    def eventQueue(self): return self._eventQueue
    @property
    def textpad(self): return self._textpad
    
    def stop(self):
        self._done.set()
    
    def run(self):
        while not self._done.is_set():
            self.textpad.edit()
            self.eventQueue.registerEvent(TextpadEvent(self.textpad.gather()))
            

class ScreenBuffer(object):
    def __init__(self, screen, size):
        self._screen = screen
        self._size = size
        self._maxLines = self.height * 40
        self._pad = curses.newpad(self._maxLines, self.width)
        self._currentLine = 0
        self._dirty = True
        
    @property
    def pad(self): return self._pad
    @property
    def size(self): return self._size
    @property
    def top(self): return self.size[0]
    @property
    def left(self): return self.size[1]
    @property
    def bottom(self): return self.size[2]
    @property
    def right(self): return self.size[3]
    @property
    def height(self): return self.bottom - self.top + 1
    @property
    def width(self): return self.right - self.left + 1
    @property
    def currentLine(self): return self._currentLine
    @property
    def dirty(self): return self._dirty
    @property
    def maxLines(self): return self._maxLines
    @property
    def currentDisplayY(self):
        if self.currentLine <= self.bottom:
            return self.top
        else:
            return self.currentLine - self.height + 1
    @property
    def currentDisplayX(self):
        return self.left
        
    def draw(self):
        self.pad.refresh(self.currentDisplayY, self.currentDisplayX, 
            self.top, self.left, self.bottom, self.right)
        self._dirty = False
    
    def writeText(self, text):
        # wrap the text to the maximum width
        wrapped_lines = textwrap.wrap(text)
        num_wrapped_lines = len(wrapped_lines)
        if self.currentLine + num_wrapped_lines >= self.maxLines:
            # scroll pad up to make room for the next set of lines
            self.pad.scroll(self.currentLine + num_wrapped_lines - self.maxLines + 1) 
        for line in wrapped_lines:
            self._pad.addstr(self._currentLine, 0, line)
            self._currentLine += 1
        self._dirty = True
        
def help():
    print "client.py <address>:<port>"

def postCommand(client, command):
    data = {'type': 'COMMAND', 'command': command}
    client.sendQueue.put_nowait(data)

def processMessage(client, display, command):
    if command['type'] == 'HEARTBEAT':
        client.heartbeatAcknowledge()
    elif command['type'] == 'PRINT':
        display.addstr(4, 1, command['message'].encode('utf_8'))
        display.refresh()
    elif command['type'] == 'DISCONNECT':
        client.stop()
    else:
        log.warn('type {0} not supported'.format(command['type']))
        log.info(repr(command))

def curses_main(screen, address, port):
    curses.start_color()
    curses.init_pair(1, curses.COLOR_GREEN, curses.COLOR_BLUE)
    curses.init_pair(2, curses.COLOR_YELLOW, curses.COLOR_BLACK)
    
    screen.bkgd(curses.color_pair(1))
    screen.refresh()
    
    entry_window = curses.newwin(5, 60, 5, 10)
    entry_window.bkgd(curses.color_pair(2))
    entry_window.box()
    
    entry_textpad = curses.textpad.Textbox(entry_window)
    
    # create an event queue where events will be processed
    eventQueue = event.EventQueue()
    
    # create keyboard poller
    textpadThread = TextpadThread(entry_textpad, eventQueue)
    textpadThread.start()

    # create the network client
    client = net.NetworkClient(address, int(port), eventQueue)
    client.start()
    
    # main event loop
    done = False
    while not done:
        try:
            if not eventQueue.noEvents:
                next_event = eventQueue.nextEvent()
                if isinstance(next_event, TextpadEvent):
                    postCommand(client, next_event.eventData)
                elif isinstance(next_event, net.NetworkReceivedDataEvent):
                    processMessage(client, screen, next_event.eventData)
                elif isinstance(next_event, net.NetworkConnectionEvent):
                    done = not next_event.eventData
            time.sleep(0.01)
        except:
            log.error(traceback.format_exc())
            break
    
    client.stop()
    textpadThread.stop()
    

def main():
    if len(sys.argv) != 2:
        help()
        sys.exit(1)
    
    try:
        address, port = sys.argv[1].split(':')
    except:
        help()
        sys.exit(1)
    
    try:
        curses.wrapper(curses_main, address, int(port))
    except:
        log.error(traceback.format_exc())
    

if __name__ == '__main__':
    main()